package com.hzhc.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpContentCompressor;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import lombok.RequiredArgsConstructor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import java.io.FileInputStream;
import java.net.InetSocketAddress;
import java.security.KeyStore;

/**
 * @ClassName: NettyServer
 * @Description:
 * @version 1.0
 * @author: duanhm
 * @Date: 2021/04/28 14:05:11
 * 
 */

@Component
@RequiredArgsConstructor
public class NettyServer {

	private static final Logger log = LogManager.getLogger(NettyServer.class);
	/**
	 * webSocket协议名
	 */
	private static final String WEBSOCKET_PROTOCOL = "WebSocket";

	/**
	 * 端口号
	 */
	@Value("${webSocket.netty.port:58080}")
	private int port;

	/**
	 * webSocket路径
	 */
	@Value("${webSocket.netty.path:/webSocket}")
	private String webSocketPath;
	/**
	 * 是否启用
	 */
	@Value("${webSocket.ishttps:false}")
	private boolean ISHTTPS = false;

	@Value("${webSocket.netty.keystorepath:D://wxpdc//config//jzh.cljport.com.pfx}")
	private String keyStorePath;
	/**
	 * keyPassword
	 */
	@Value("${webSocket.netty.keypassword:1nG4ACVE}")
	private String keyPassword;

	private final WebSocketHandler webSocketHandler;

	private EventLoopGroup bossGroup;
	private EventLoopGroup workGroup;

	/**
	 * 启动
	 * 
	 * @throws InterruptedException
	 */
	private void start() throws InterruptedException {
		bossGroup = new NioEventLoopGroup();
		workGroup = new NioEventLoopGroup();
		ServerBootstrap bootstrap = new ServerBootstrap();
		// bossGroup辅助客户端的tcp连接请求, workGroup负责与客户端之前的读写操作
		bootstrap.group(bossGroup, workGroup);
		// 设置NIO类型的channel
		bootstrap.channel(NioServerSocketChannel.class);
		// 设置监听端口
		bootstrap.localAddress(new InetSocketAddress(port));
		// 连接到达时会创建一个通道
		bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {

			@Override
			protected void initChannel(SocketChannel ch) throws Exception {
				if (ISHTTPS) {
					// =====================以下为SSL处理代码=================================
					log.info("开始设置https服务");
					log.info("keyStorePath===" + keyStorePath);
					SSLContext sslcontext = SSLContext.getInstance("TLS");
					KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
					KeyStore ks = KeyStore.getInstance("PKCS12");
					// String keyStorePath = "D:\\wxpdc-test\\config\\jzh.cljport.com.pfx";
					// keyPassword = "1nG4ACVE";
					ks.load(new FileInputStream(keyStorePath), keyPassword.toCharArray());
					kmf.init(ks, keyPassword.toCharArray());
					sslcontext.init(kmf.getKeyManagers(), null, null);
					SSLEngine engine = sslcontext.createSSLEngine();
					engine.setUseClientMode(false);
					engine.setNeedClientAuth(false);
					ch.pipeline().addFirst("ssl", new SslHandler(engine));
					// =====================以上为SSL处理代码=================================
					ch.pipeline().addLast(new HttpContentCompressor(1));
				}
				// 流水线管理通道中的处理程序（Handler），用来处理业务
				// webSocket协议本身是基于http协议的，所以这边也要使用http编解码器
				ch.pipeline().addLast(new HttpServerCodec());
				ch.pipeline().addLast(new ObjectEncoder());
				// 以块的方式来写的处理器
				ch.pipeline().addLast(new ChunkedWriteHandler());
				/*
				 * 说明： 1、http数据在传输过程中是分段的，HttpObjectAggregator可以将多个段聚合
				 * 2、这就是为什么，当浏览器发送大量数据时，就会发送多次http请求
				 */
				ch.pipeline().addLast(new HttpObjectAggregator(8192));
				/*
				 * 说明： 1、对应webSocket，它的数据是以帧（frame）的形式传递 2、浏览器请求时 ws://localhost:58080/xxx
				 * 表示请求的uri 3、核心功能是将http协议升级为ws协议，保持长连接
				 */
				ch.pipeline().addLast(
						new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10));
				// 自定义的handler，处理业务逻辑
				ch.pipeline().addLast(webSocketHandler);

			}
		});
		// 配置完成，开始绑定server，通过调用sync同步方法阻塞直到绑定成功
		ChannelFuture channelFuture = bootstrap.bind().sync();
		log.info("Server started and listen on:{}", channelFuture.channel().localAddress());
		// 对关闭通道进行监听
		channelFuture.channel().closeFuture().sync();
	}

	/**
	 * 释放资源
	 * 
	 * @throws InterruptedException
	 */
	@PreDestroy
	public void destroy() throws InterruptedException {
		if (bossGroup != null) {
			bossGroup.shutdownGracefully().sync();
		}
		if (workGroup != null) {
			workGroup.shutdownGracefully().sync();
		}
	}

	@PostConstruct()
	public void init() {
		// 需要开启一个新的线程来执行netty server 服务器
		new Thread(() -> {
			try {
				start();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}).start();
	}
}
